#include <sourcemod>
#include <smlib>

#define DEBUG 1
#define DEBUG_LOG 1

public Plugin:myinfo = 
{
	name = "PluginHelper",
	author = "Mother Goose",
	description = "Plugin Helper",
	version = "1.0",
	url = "www.left4noobs.com"
}

enum ActionType
{
	ActionType_Load=0,
	ActionType_Reload=1,
	ActionType_Unload=2,
	ActionType_Reset=3,
	ActionType_UnloadAll=4
}

enum PluginStatusEx
{
	PluginStatusEx_Running=0,		/**< Plugin is running */
	/* All states below are "temporarily" unexecutable */
	PluginStatusEx_Paused,			/**< Plugin is loaded but paused */
	PluginStatusEx_Error,			/**< Plugin is loaded but errored/locked */
	/* All states below do not have all natives */
	PluginStatusEx_Loaded,			/**< Plugin has passed loading and can be finalized */
	PluginStatusEx_Failed,			/**< Plugin has a fatal failure */
	PluginStatusEx_Created,			/**< Plugin is created but not initialized */
	PluginStatusEx_Uncompiled,		/**< Plugin is not yet compiled by the JIT */
	PluginStatusEx_BadLoad,			/**< Plugin failed to load */
	PluginStatusEx_NotLoaded,			/**< Plugin is not loaded */
};

new Handle:g_adtStaticDirs = INVALID_HANDLE;
new Handle:g_adtStaticFiles = INVALID_HANDLE;

public OnPluginStart()
{
	Debug("Plugin Started");
	
	RegServerCmd("ph_add",Command_AddStatic, "Marks a specified file or directory as static");
	RegServerCmd("ph_clear_all", Command_ClearAllStaticEntries, "Clears all static files and directories");
	RegServerCmd("ph_clear_dirs", Command_ClearStaticDirs, "Clears all static directories");
	RegServerCmd("ph_clear_files", Command_ClearStaticFiles, "Clears all marked static files");
	RegServerCmd("ph_refresh_dir", Command_ReloadPluginsFromDir, "Re-loads/Refresh all plugins contained in the specified directory");
	RegServerCmd("ph_reset", Command_ResetPlugins, "Unloads all plugins and restores back to the default plugins.");
	RegServerCmd("ph_load_dir", Command_LoadPluginsFromDir, "Load all plugins contained in the specified directory");
	RegServerCmd("ph_unload_all", Command_UnloadAll, "Unloads all plugins except for the static ones.");
	
	g_adtStaticDirs = CreateArray(24);
	g_adtStaticFiles = CreateArray(24);
}

public OnPluginEnd()
{
	Debug("Plugin End");
}

public Action:Command_LoadPluginsFromDir(args)
{
	decl String:dirName[255];
	
	if (args <= 0)
	{
		decl String:arg0[32];
		GetCmdArg(0, arg0, sizeof(arg0));
		PrintToServer("Usage: %s <directory name>", arg0);
		return Plugin_Handled;
	}
		
	//Process each argument
	for (new i = 1; i<=args; i++)
	{
		GetCmdArg(i, dirName, sizeof(dirName));
		
		//Debug("Loading Plugins From Directory");
		PerformFolderAction(dirName, ActionType_Load);		
	}
		
	return Plugin_Handled;
}

public Action:Command_ReloadPluginsFromDir(args)
{
	//Loop throgh the loaded plugins then unload one by one except if plugin is static
	new Handle:iter = GetPluginIterator();
	
	while (MorePlugins(iter))
	{
		new Handle:hPlugin = ReadPlugin(iter);
		decl String:pluginFileName[64];
		GetPluginFilename(hPlugin, pluginFileName, sizeof(pluginFileName));
		if (!IsPluginFileStatic(pluginFileName))
		{
			PerformFileAction(pluginFileName, ActionType_Reload);
		}
	}
	
	CloseHandle(iter);
	
	return Plugin_Handled;
}

public Action:Command_ClearStaticDirs(args)
{
	ClearArray(g_adtStaticFiles);
	Debug("Cleared Static Directories");
	return Plugin_Handled;
}

public Action:Command_ClearStaticFiles(args)
{
	ClearArray(g_adtStaticDirs);
	Debug("Cleared Static Directories");
	return Plugin_Handled;
}

public Action:Command_ClearAllStaticEntries(args)
{
	ClearArray(g_adtStaticDirs);
	ClearArray(g_adtStaticFiles);
	Debug("Cleared Static Files and Directories");
	return Plugin_Handled;
}

public Action:Command_UnloadAll(args)
{
	UnloadAllPlugins();
	return Plugin_Handled;
}

public Action:Command_ResetPlugins(args)
{
	Debug("Resetting Plugins back to its default state");
	
	ServerCommand("sm plugins load_unlock");
	UnloadAllPlugins();
	ServerCommand("sm plugins refresh");
	
	return Plugin_Handled;
}

public Action:Command_AddStatic(args)
{
	decl String:fileName[255];
	
	if (args <= 0)
	{
		decl String:arg0[32];
		GetCmdArg(0, arg0, sizeof(arg0));
		PrintToServer("Usage: %s <filename1> <filename2> <filename3>", arg0);
		return Plugin_Handled;
	}
	
	//Process each argument
	for (new i = 1; i<=args; i++)
	{
		GetCmdArg(i, fileName, sizeof(fileName));
		
		//Determine if file or directory
		Debug("%i) Checking File Type: %s", i, fileName);
		if (StrContains(fileName, ".smx", false) > -1)
		{
			AddStaticFile(fileName);
		}
		else
		{
			AddStaticDir(fileName);
		}
	}
	
	return Plugin_Handled;
}

public bool:AddStaticFile(const String:fileName[])
{
	decl String:fullFilePath[255];
	
	//Check if it exists in the list
	if (IsFileInStaticList(fileName)) 
	{
		Debug("File %s is already on the list", fileName);
		return false;
	}
	
	//Build Plugin Path
	BuildPluginDirPath(fullFilePath, sizeof(fullFilePath), fileName);
	
	Debug("Marking as Static File # %s (%s)", fileName, fullFilePath);
	
	if (!FileExists(fullFilePath))
	{
		Debug("File '%s' does not exist. Skipping Entry.", fullFilePath);
		return false;
	}
	
	//Add to array
	PushArrayString(g_adtStaticFiles, fileName);
	Debug("Successfully Added Static File = %s", fileName);
		
	return true;
}

public bool:AddStaticDir(const String:dirName[])
{
	decl String:fullDirPath[255];
	
	//Check if it exists in the list
	if (IsDirInStaticList(dirName)) 
	{
		Debug("Directory %s is already on the list", dirName);
		return false;
	}
	
	//Build Plugin Path
	BuildPluginDirPath(fullDirPath, sizeof(fullDirPath), dirName);
	
	Debug("Marking as Static Dir: %s (%s)", dirName, fullDirPath);
	
	if (!DirExists(fullDirPath))
	{
		Debug("Directory '%s' does not exist. Skipping Entry.", fullDirPath);
		return false;
	}
	
	//Add to array
	PushArrayString(g_adtStaticDirs, dirName);
	Debug("Successfully Added Static Directory = %s", dirName);
	
	return true;
}

public UnloadAllPlugins()
{
	new Handle:iter = GetPluginIterator();
	
	new pNum = 1;
	
	while (MorePlugins(iter))
	{
		new Handle:hPlugin = ReadPlugin(iter);
		decl String:pFName[255];
		decl String:pName[64];
		
		GetPluginFilename(hPlugin, pFName, sizeof(pFName));
		GetPluginInfo(hPlugin, PlInfo_Name, pName, sizeof(pName));
		
		//Do not unload this plugin (doh)
		if (!StrEqual(pName, "PluginHelper"))
		{
			if (!IsPluginFileStatic(pFName))
			{
				Debug("Unloading Plugin: %s/%s (%i)", pFName, pName, pNum);
				ServerCommand("sm plugins unload %s", pFName);
			}
			else
			{
				Debug("Unable to unload plugin '%s'. Plugin is marked as STATIC.", pFName);
			}
		}
		
		CloseHandle(hPlugin);
		pNum++;
	}
	CloseHandle(iter);	
}

public BuildPluginDirPath(String:buffer[], maxlen, const String:dirName[])
{
	BuildPath(Path_SM, buffer, maxlen, "plugins/%s", dirName);
}

public bool:IsDirInStaticList(const String:dirName[])
{
	new index = FindStringInArray(g_adtStaticDirs, dirName);	
	if (index > -1) return true;
	return false;
}

public bool:IsFileInStaticList(const String:fileName[])
{
	new index = FindStringInArray(g_adtStaticFiles, fileName);	
	if (index > -1) return true;
	return false;
}

public bool:IsPluginFileStatic(String:pluginFileName[])
{
	//Check first if the plugin is marked on the static file list
	if (IsFileInStaticList(pluginFileName))
	{
		//Debug("File is marked as static: %s", pluginFileName);
		return true;
	}
	
	//If file is not individually marked as static, proceed checking if it is associated with a directory marked as static
	if (GetArraySize(g_adtStaticDirs) <= 0) 
	{
		Debug("WARN: No static directories defined");
		return false;
	}
		
	//Loop through the static directories then check to see if the plugin file is there
	for (new i = 0; i < GetArraySize(g_adtStaticDirs); i++)
	{
		decl String:staticDirName[255];
		GetArrayString(g_adtStaticDirs, i, staticDirName, sizeof(staticDirName));
		
		decl String:dirPath[255];
		BuildPluginDirPath(dirPath, sizeof(dirPath), staticDirName);
		
		//Debug("Searching Static Directory %s for file %s", dirPath, pluginFileName);
		
		//Check if the plugin dir is valid
		if (DirExists(dirPath))
		{
			new Handle:hDir = OpenDirectory(dirPath);
			new FileType:fileType;
			decl String:fileName[255];
			
			while (ReadDirEntry(hDir, fileName, sizeof(fileName), fileType))
			{
				if (fileType == FileType_File)
				{
					decl String:pluginBaseName[255];
					GetBaseName(pluginFileName, pluginBaseName, sizeof(pluginBaseName));
					
					//Debug("-- Search File: %s = %s", fileName, pluginBaseName);
					
					if (StrEqual(fileName, pluginBaseName))
					{
						CloseHandle(hDir);
						return true;
					}
				}
			}
			CloseHandle(hDir);
		}
		else
		{
			Debug("Error: Static Directory %s does not exist", dirPath);
			continue;
		}
		Debug("Found Static Dir: %s", staticDirName);
	}
	return false;
}

stock bool:GetBaseName(const String:path[], String:buffer[], size)
{
	if (path[0] == '\0') {
		buffer[0] = '\0';
		return;
	}
	new pos_start = FindCharInString(path, '/', true);

	if (pos_start == -1) {
		pos_start = FindCharInString(path, '\\', true);
	}
	pos_start++;
	strcopy(buffer, size, path[pos_start]);
}

public PluginStatusEx:GetPluginStatusFromFileName(String:fileName[])
{
	//Loop through the current running plugins and check if its running
	new Handle:iter = GetPluginIterator();
	while (MorePlugins(iter))
	{
		new Handle:hPlugin = ReadPlugin(iter);
		new String:fNameLoaded[255];
		GetPluginFilename(hPlugin, fNameLoaded, sizeof(fNameLoaded));
		if (StrEqual(fileName, fNameLoaded))
		{
			Debug("Found Loaded Plugin %s", fNameLoaded);
			CloseHandle(hPlugin);
			return ConvertPluginStatus(GetPluginStatus(hPlugin));
		}
		CloseHandle(hPlugin);
	}
	CloseHandle(iter);
	return PluginStatusEx_NotLoaded;
}
	
public PluginStatusEx:ConvertPluginStatus(PluginStatus:status)
{
	switch (status)
	{
		case Plugin_Running:
		{
			return PluginStatusEx_Running;
		}
		case Plugin_Paused:
		{
			return PluginStatusEx_Paused;
		}
		case Plugin_Error:
		{
			return PluginStatusEx_Error;
		}
		case Plugin_Loaded:
		{
			return PluginStatusEx_Loaded;
		}
		case Plugin_Failed:
		{
			return PluginStatusEx_Failed;
		}
		case Plugin_Created:
		{
			return PluginStatusEx_Created;
		}
		case Plugin_Uncompiled:
		{
			return PluginStatusEx_Uncompiled;
		}
		case Plugin_BadLoad:
		{
			return PluginStatusEx_BadLoad;
		}
	}	
	return PluginStatusEx_NotLoaded;
}
	
public PerformFolderAction(String:dirName[], ActionType:action)
{
	new String:dirPath[255];
	new String:fileName[255];
	new FileType:fileType = FileType_Unknown;
	new Handle:hDir = INVALID_HANDLE;
	
	BuildPluginDirPath(dirPath, sizeof(dirPath), dirName);
	
	if (!DirExists(dirPath))	
	{
		Debug("Directory '%s' does not exist", dirName);
		return;
	}
	
	hDir = OpenDirectory(dirPath);
	
	if (hDir == INVALID_HANDLE) 
	{
		Debug("Invalid Directory Handle. Exiting..");
		return;
	}
	
	Debug("Processing Folder: %s with Action Type: %i", dirPath, action);
	
	while (ReadDirEntry(hDir, fileName, sizeof(fileName), fileType))
	{
		if (fileType == FileType_File)
		{
			decl String:filePath[255];
			
			Format(filePath, sizeof(filePath), "%s/%s", dirName, fileName);
			PerformFileAction(filePath, action);
		}
	}
	
	CloseHandle(hDir);
	
}
	
public ProcessRunningPlugins()
{
	Debug("From Plugin Iterator...");
	
	new Handle:iter = GetPluginIterator();
	
	while (MorePlugins(iter))
	{
		new Handle:hPlugin = ReadPlugin(iter);
		new String:fName[255];
		
		GetPluginFilename(hPlugin, fName, sizeof(fName));
		
		decl String:pStatusDesc[255];
		GetPluginStatusDesc(hPlugin, pStatusDesc, sizeof(pStatusDesc));
		Debug("Found Plugin: %s, Status: %s", fName, pStatusDesc);
		
		CloseHandle(hPlugin);
	}
	
	CloseHandle(iter);	
}

public GetPluginStatusDesc(Handle:hPlugin, String:buffer[], maxlen)
{
	new PluginStatusEx:status = ConvertPluginStatus(GetPluginStatus(hPlugin));
	GetPluginStatusDescFromCode(status, buffer, maxlen);
}
	
public GetPluginStatusDescFromCode(PluginStatusEx:statusCode, String:buffer[], maxlen)
{
	switch (statusCode)
	{
		case PluginStatusEx_Running:
		{
			Format(buffer, maxlen, "Running");
		}
		case PluginStatusEx_Paused:
		{
			Format(buffer, maxlen, "Paused");
		}
		case PluginStatusEx_Error:
		{
			Format(buffer, maxlen, "Error");
		}
		case PluginStatusEx_Loaded:
		{
			Format(buffer, maxlen, "Loaded");
		}
		case PluginStatusEx_Failed:
		{
			Format(buffer, maxlen, "Failed");
		}
		case PluginStatusEx_Created:
		{
			Format(buffer, maxlen, "Created");
		}
		case PluginStatusEx_Uncompiled:
		{
			Format(buffer, maxlen, "Uncompiled");
		}
		case PluginStatusEx_BadLoad:
		{
			Format(buffer, maxlen, "Failed to Load");
		}
		case PluginStatusEx_NotLoaded:
		{
			Format(buffer, maxlen, "Not Loaded");
		}
		default:
		{
			Format(buffer, maxlen, "Unknown");
		}
	}
}
	
public PerformFileAction(String:fileName[], ActionType:action)
{
	Debug("File: %s, ActionType: %i", fileName, action);
	
	switch (action)
	{
		//Load Plugin
		case ActionType_Load:
		{
			ServerCommand("sm plugins load %s", fileName);
		}
		//Re-load Plugin
		case ActionType_Reload:
		{
			ServerCommand("sm plugins reload %s", fileName);
		}
		//Unload Plugin
		case ActionType_Unload:
		{
			ServerCommand("sm plugins unload %s", fileName);
		}
		case ActionType_Reset:
		{
			//
		}
	}
}
	
stock Debug(const String:format[], any:...)
{
	#if DEBUG || DEBUG_LOG
	decl String:buffer[192];
	
	VFormat(buffer, sizeof(buffer), format, 2);
	
	#if DEBUG
	PrintToChatAll("[PLUGINMAN] %s", buffer);
	#endif
	
	LogMessage("[PLUGINMAN] %s", buffer);
	#else
	//suppress "format" never used warning
	if(format[0])
		return;
	else
	return;
	#endif
}